<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>ECMAScript 对象 定义类或对象</title>
    <script>
      /*
      使用预定义对象只是面向对象语言的能力的一部分，它真正强大之处在于能够创建自己专用的类和对象。
      ECMAScript 拥有很多创建对象或类的方法。
      */
      /*
      Js 中也有工厂模式 同一种思想可以使用不同的语言表达 不论是Java 还是 OC 都有类似的语言
      下面我们一步步来做这种推进
      */

      // 原始的方法 
      function originalObject(){

        var oCar = new Object;
        oCar.color = "blue";
        oCar.doors = 4;
        oCar.mpg = 25;
        oCar.showColor = function() {
        alert(this.color);
        };
        oCar.showColor();
      }


      function createCar() {
        var oTempCar = new Object;
        oTempCar.color = "blue";
        oTempCar.doors = 4;
        oTempCar.mpg = 25;
        oTempCar.showColor = function() {
            alert(this.color);
        };
        return oTempCar;
      }

      function testCreateCar(){
        var oCar1 = createCar();
        var oCar2 = createCar();
        oCar1.showColor()
        oCar2.showColor()
      }

      function createCarTwo(sColor,iDoors,iMpg) {
        var oTempCar = new Object;
        oTempCar.color = sColor;
        oTempCar.doors = iDoors;
        oTempCar.mpg = iMpg;
        oTempCar.showColor = function() {
            alert(this.color);
        };
        return oTempCar;
      }

      function testCreateCarTwo(){

        var oCar1 = createCarTwo("red",4,23);
        var oCar2 = createCarTwo("blue",3,25);

        oCar1.showColor();		//输出 "red"
        oCar2.showColor();		//输出 "blue"
      }

      /*
      在工厂函数外定义对象的方法
    虽然 ECMAScript 越来越正式化，但创建对象的方法却被置之不理，且其规范化至今还遭人反对。一部分是语义上的原因
    （它看起来不像使用带有构造函数 new 运算符那么正规），一部分是功能上的原因。功能原因在于用这种方式必须创建对象的方法。
    前面的例子中，每次调用函数 createCar()，都要创建新函数 showColor()，意味着每个对象都有自己的 showColor() 版本。而事实上，
    每个对象都共享同一个函数。

    有些开发者在工厂函数外定义对象的方法，然后通过属性指向该方法，从而避免这个问题：
      */
      function showColor() {
        alert(this.color);
      }

      function createCarFour(sColor,iDoors,iMpg) {
        var oTempCar = new Object;
        oTempCar.color = sColor;
        oTempCar.doors = iDoors;
        oTempCar.mpg = iMpg;
        oTempCar.showColor = showColor;  // 在JS中 函数也是对象 
        return oTempCar;
     }

     function testCreateCarFour(){
        var oCar1 = createCar("red",4,23);
        var oCar2 = createCar("blue",3,25);

        oCar1.showColor();		//输出 "red"
        oCar2.showColor();		//输出 "blue"
     }

     /*
     构造函数方式
    创建构造函数就像创建工厂函数一样容易。第一步选择类名，即构造函数的名字。根据惯例，
    这个名字的首字母大写，以使它与首字母通常是小写的变量名分开。除了这点不同，构造函数看起来很像工厂函数。请考虑下面的例子：
     */
     function Car(sColor,iDoors,iMpg) {
        this.color = sColor;  // 使用了 this 关键字
        this.doors = iDoors;
        this.mpg = iMpg;
        this.showColor = function() {
            alert(this.color);  
        };  // 最后 没有明确的使用 return
     }

     function testInstanceTypeCreate(){

        var oCar1 = new Car("red",4,23);
        var oCar2 = new Car("blue",3,25);
        oCar1.showColor()
        oCar2.showColor()   // 同样 showColor 每个对象里面都有  
     }

     /*
      使用 原型方法 也就是 prototype
     */
     function Bmw(){   // bmw 函数 

     }

     Bmw.prototype.color = "blue";
     Bmw.prototype.doors = 4;
     Bmw.prototype.mpg = 25;
     Bmw.prototype.showColor = function() {
      alert(this.color);
     };

     function testPrototype(){
        var oCar1 = new Bmw();
        var oCar2 = new Bmw();
        oCar1.showColor()
        oCar2.showColor()

        if (oCar1 instanceof Bmw){
            alert('属于 Bmw 类')
        }else{
            alert('不属于 Bmw 类')
        }
     }

     /*
     使用原型方式，不能通过给构造函数传递参数来初始化属性的值，
     因为 Car1 和 Car2 的 color 属性都等于 "blue"，doors 属性都等于 4，mpg 属性都等于 25。
     这意味着必须在对象创建后才能改变属性的默认值，这点很令人讨厌，但还没完。真正的问题出现在属性指向的是对象，
     而不是函数时。函数共享不会造成问题，但对象却很少被多个实例共享。请思考下面的例子：

     */
     function Benzi(){

     }


     Benzi.prototype.color = "blue";
     Benzi.prototype.doors = 4;
     Benzi.prototype.mpg = 25;
     Benzi.prototype.drivers = new Array("Mike","John");
     Benzi.prototype.showColor = function() {
        alert(this.color);
     };

     function testBenzi(){
         var benzi = new Benzi()
         var benzi2 = new Benzi()
         
         benzi.drivers.push("Bill");

         alert(benzi.drivers);	//输出 "Mike,John,Bill"
         alert(benzi2.drivers);	//输出 "Mike,John,Bill"

     }


     /*
     上面的代码中，属性 drivers 是指向 Array 对象的指针，该数组中包含两个名字 "Mike" 和 "John"。由于 drivers 是引用值，
     Car 的两个实例都指向同一个数组。这意味着给 oCar1.drivers 添加值 "Bill"，在 oCar2.drivers 中也能看到。
     输出这两个指针中的任何一个，结果都是显示字符串 "Mike,John,Bill"。由于创建对象时有这么多问题，你一定会想，是否有种合理的创建对象的方法呢？答案是有，需要联合使用构造函数和原型方式。
     */

     /*
     下面使用构造函数 和 原型方法 混合的构造函数和方法
     */
     function Mixcar(sColor,iDoors,iMpg){
        this.color = sColor;
        this.doors = iDoors;
        this.mpg = iMpg;
        this.drivers = new Array("Mike","John");
     }

     Mixcar.prototype.showColor = function(){
         alert(this.color)
     }

     function testMixCar(){
        var mixCar1 = new Mixcar('red','msg',20);
        var mixCar2 = new Mixcar('blue','sss',33);
        mixCar1.drivers.push('Bill');
        alert(mixCar1.drivers);
        alert(mixCar2.drivers);
       mixCar1.showColor()
       mixCar2.showColor()
     }
    
    /*
    动态原型方法 
    来是的 Js 的类的定义更加具有 封装和 其他面向对像的语言相似  这个挺有
    */
     function  Tesla(sColor,iDoors,iMpg){
        this.color = sColor;
        this.doors = iDoors;
        this.mpg = iMpg;
        this.drivers = new Array("Mike","John");

        if (typeof Tesla._initialized == 'undefined'){  
            
        /*
        直到检查 typeof Car._initialized 是否等于 "undefined" 之前，这个构造函数都未发生变化。这行代码是动态原型方法中最重要的部分。如果这个值未定义，构造函数将用原型方式继续定义对象的方法，然后把 Car._initialized 设置为 true。如果这个值定义了（它的值为 true 时，typeof 的值为 Boolean），那么就不再创建该方法。简而言之，该方法使用标志（_initialized）来判断是否已给原型赋予了任何方法。该方法只创建并赋值一次，传统的 OOP 开发者会高兴地发现，这段代码看起来更像其他语言中的类定义了
        */

            Tesla.prototype.showColor = function (){
                alert('Tesla'  + this.color)
            }
        }

        Tesla._initialized = true
     }

     function testTesla(){

        var teslaOne = new Tesla('red','door',33)
        alert(teslaOne.drivers)
        teslaOne.showColor()
        teslaOne.drivers.push('DeLong')
        alert(teslaOne.drivers)
     }

     /*
     性能优化 

     */
     function testStringConcat(){
        var str = 'hello'
        str += 'world'
         /*
         创建存储 "hello " 的字符串。
        创建存储 "world" 的字符串。
        创建存储连接结果的字符串。
        把 str 的当前内容复制到结果中。
        把 "world" 复制到结果中。
        更新 str，使它指向结果。
        每次完成字符串连接都会执行步骤 2 到 6，使得这种操作非常消耗资源。如果重复这一过程几百次，甚至几千次，
        就会造成性能问题。解决方法是用 Array 对象存储字符串，然后用 join() 方法（参数是空字符串）创建最后的字符串。
        想象用下面的代码代替前面的代码
         */

         var arr = new Array()
         arr[0] = 'hello'
         arr[1] = 'world'
         var str2 = arr.join("")
         alert('array joint is:' + str2)
       }
         /*
        这样，无论数组中引入多少字符串都不成问题，因为只在调用 join() 方法时才会发生连接操作。此时，执行的步骤如下：

        创建存储结果的字符串
        把每个字符串复制到结果中的合适位置
        虽然这种解决方案很好，但还有更好的方法。问题是，这段代码不能确切反映出它的意图。要使它更容易理解，
        可以用 StringBuffer 类打包该功能：
         */

        function StringBuffer (){
            this._strings_ = new Array();
        }

        StringBuffer.prototype.append = function(str){
            this._strings_.push(str)
        }

        StringBuffer.prototype.toString = function(){
            return this._strings_.join("")
        }

        /*
        ！！！！！！  注意这里并没有 得出相应的结果 不知道 Java 中的是否也是一样 ！！！！！！！！
        这段代码首先要注意的是 strings 属性，本意是私有属性。它只有两个方法，即 append() 和 toString() 方法。
        append() 方法有一个参数，它把该参数附加到字符串数组中，toString() 方法调用数组的 join 方法，
        返回真正连接成的字符串。要用 StringBuffer 对象连接一组字符串，可以用下面的代码：
        */
        function testStringBuffer(){
            var buffer = new StringBuffer()
            buffer.append('Hello')
            buffer.append('DeLong')
            var result = buffer.toString()
            alert(result)

            // 可用下面的代码测试 StringBuffer 对象和传统的字符串连接方法的性能：
            var d1 = new Date();
            var str = "";
            for (var i=0; i < 10000; i++) {
                str += "text";
            }
            var d2 = new Date();

            document.write("Concatenation with plus: "
            + (d2.getTime() - d1.getTime()) + " milliseconds");

            var buffer = new StringBuffer();
            d1 = new Date();
            for (var i=0; i < 10000; i++) {
                buffer.append("text");
            }
            var result = buffer.toString();
            d2 = new Date();

            document.write("<br />Concatenation with StringBuffer: "
            + (d2.getTime() - d1.getTime()) + " milliseconds");

            //  实际上结果和所说的不一样  ！！！！！！！！  使用buffer 的的时间更长   而 str 的时间更短 
           
        }
    </script>
</head>
<body>
    <button onclick="originalObject()">originalObject</button>
    <button onclick="testCreateCar()">testCreateCar</button>
    <button onclick="testCreateCarTwo()">testCreateCarTwo</button>
    <button onclick="testCreateCarFour()">testCreateCarFour</button>
    <button onclick="testInstanceTypeCreate()">testInstanceTypeCreate</button>
    <button onclick="testPrototype()">testInstanceTypeCreate</button>
    <button onclick="testBenzi()">testBenzi</button>
    <button onclick="testMixCar()">testMixCar</button>
    <button onclick="testTesla()">testTesla</button>
    <button onclick="testStringBuffer()">testStringBuffer  字符串性能优化</button>
</body>
</html>